package com.zeng.ssm.service.support.trans.base;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.CharsetUtil;
import com.zeng.ssm.api.support.idempotent.DistributedLockService;
import com.zeng.ssm.dal.mapper.AccountLogMapper;
import com.zeng.ssm.dal.mapper.AccountMsgLogMapper;
import com.zeng.ssm.dal.model.AccountLog;
import com.zeng.ssm.dal.model.AccountLogExample;
import com.zeng.ssm.dal.model.AccountMsgLog;
import com.zeng.ssm.dal.model.AccountMsgLogExample;
import com.zeng.ssm.service.support.trans.GxpConstant;
import com.zeng.ssm.service.support.trans.TransConstant;
import com.zeng.ssm.service.support.trans.annotation.ClientHandler;
import com.zeng.ssm.util.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 客户端-处理Handler
 *
 * @author zengwanlin
 * @date 2023-08-30T16:10
 */
public abstract class BaseClientHandler implements IClientHandler {

    private Logger logger = LoggerFactory.getLogger(BaseClientHandler.class);

    @Autowired
    private DistributedLockService distributedLockService;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private AccountLogMapper accountLogMapper;

    @Autowired
    private AccountMsgLogMapper accountMsgLogMapper;

    /**
     * 交易组件
     */
    protected BasicTransComponent component;

    /**
     * 上一次交易记录
     */
    private AccountLog lastAccountLog;

    /**
     * 上一次交易日志记录
     */
    private AccountMsgLog lastAccountMsgLog;

    /**
     * 当前交易记录
     */
    private AccountLog currAccountLog;

    /**
     * 当前交易日志记录
     */
    private AccountMsgLog currAccountMsgLog;

    /**
     * 作为客户端，消息发送是否已完成
     * <p>
     * true-已完成，false-未完成
     */
    private boolean sendMsgComplete;

    /**
     * 交易日期
     */
    protected final String tranDate = DateUtil.getDate8();

    /**
     * 交易时间
     */
    protected final String tranTime = DateUtil.getTime6();

    /**
     * 作为客户端-触发接口调用
     *
     * @param transComponent
     */
    @Override
    public void handle(BasicTransComponent transComponent) {
        // 1.初始化交易组件
        logger.info("初始化交易组件");
        init(transComponent);

        // 2.加锁
        logger.info("请求加锁");
        ImmutablePair<String, String> lockPair = lock();
        try {
            TransactionTemplate template = new TransactionTemplate(transactionManager);
            template.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
            template.executeWithoutResult((transactionStatus) -> {
                // 3.防重检查
                if (component.isUnRepeatable()) {
                    logger.info("防重检查");
                    if (succeed()) {
                        // 当前已成功
                        if (component.isReParse()) {
                            // 需要再次解析响应报文
                            reParseResponseMsg();
                        }
                        component.setSuccess(true);
                        component.setRetMsg("已有成功记录");
                        return;
                    }
                }

                // 4.组装请求报文
                logger.info("组装请求报文");
                buildRequestMsg();
                Assert.hasText(component.getReqSeqNo(), "请求流水号未设置");
                Assert.hasText(component.getReqMsg(), "请求报文未设置");
                logger.info("请求报文[{}]", component.getReqMsg());

                // 5.记录交易日志
                logger.info("记录交易日志");
                writeAccountLog();

                // 6.发送报文
                logger.info("发送报文");
                sendMsg();
                sendMsgComplete = true;
                Assert.hasText(component.getRespMsg(), "响应报文未设置");
                logger.info("响应报文[{}]", component.getRespMsg());

                // 7.解析响应报文
                logger.info("解析响应报文");
                parseResponseMsg();
                Assert.hasText(component.getRespSeqNo(), "响应流水号未设置");

                // 8.更新交易日志
                logger.info("更新交易日志");
                updateAccountLog(null);

            });
        } catch (Throwable th) {
            logger.info("异常，更新交易日志");
            updateAccountLog(th);
            logger.error("交易异常", th);
            throw new IllegalStateException(th);
        } finally {
            // 7.解锁
            logger.info("请求解锁");
            unlock(lockPair);
        }
    }

    /**
     * 初始化
     *
     * @param transComponent 交易组件
     */
    protected void init(BasicTransComponent transComponent) {
        // 参数检查
        Assert.notNull(transComponent, "参数不能为空");
        Assert.notNull(transComponent.getContractId(), "批次ID不能为空");
        Assert.notNull(transComponent.getDetailId(), "明细ID不能为空");
        Assert.hasText(transComponent.getProductNo(), "产品号不能为空");

        this.sendMsgComplete = false;

        ClientHandler annotation = getClass().getDeclaredAnnotation(ClientHandler.class);
        if (annotation == null) {
            throw new IllegalArgumentException("类" + getClass().getName() + "未标注注解：@" + ClientHandler.class.getSimpleName());
        }
        if (annotation.reParse() && ClientHandler.LoggingType.NONE.equals(annotation.loggingType())) {
            throw new IllegalArgumentException("注解[" + ClientHandler.class.getSimpleName() + "]属性设置有误，reParse为true时必须保存报文记录");
        }
        logger.info("当前交易为[{}]", annotation.msgName());
        transComponent.setSuccess(false);
        transComponent.setSysCode(annotation.sysCode());
        transComponent.setMsgCode(annotation.msgCode());
        transComponent.setMsgName(annotation.msgName());
        transComponent.setLoggingType(annotation.loggingType());
        transComponent.setUnRepeatable(annotation.unRepeatable());
        transComponent.setReParse(annotation.reParse());
        this.component = transComponent;
    }

    /**
     * 加锁
     *
     * @return left：lock_key，right：lock_value
     */
    protected ImmutablePair<String, String> lock() {
        final long lockTimeout = 120L;
        String lockKey = component.getProductNo() + component.getContractId() + component.getDetailId();
        String lockValue = UUID.randomUUID().toString();
        if (distributedLockService.lock(lockKey, lockValue, lockTimeout)) {
            return ImmutablePair.of(lockKey, lockValue);
        }
        throw new IllegalStateException("交易处理中，请勿重复提交");
    }

    /**
     * 解锁
     *
     * @param lockPair left：lock_key，right：lock_value
     * @return true-解锁成功，false-解锁失败
     */
    protected boolean unlock(ImmutablePair<String, String> lockPair) {
        if (lockPair != null) {
            return distributedLockService.unlock(lockPair.getLeft(), lockPair.getRight());
        }
        return false;
    }

    /**
     * 成功检查，判断是否已成功
     *
     * @return true-已成功，false-未成功
     */
    private boolean succeed() {
        AccountLogExample example = new AccountLogExample();
        example.createCriteria().andContractIdEqualTo(component.getContractId()).andDetailIdEqualTo(component.getDetailId()).andProductNoEqualTo(component.getProductNo());
        example.setOrderByClause("ID desc");
        List<AccountLog> accountLogList = accountLogMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(accountLogList)) {
            return false;
        }
        lastAccountLog = accountLogList.get(0);
        AccountMsgLogExample msgLogExample = new AccountMsgLogExample();
        msgLogExample.createCriteria().andAcctIdEqualTo(lastAccountLog.getId()).andTranDateEqualTo(lastAccountLog.getTranDate()).andTranTimeEqualTo(lastAccountLog.getTranTime());
        msgLogExample.setOrderByClause("ID desc");
        RowBounds rowBounds = new RowBounds(RowBounds.NO_ROW_OFFSET, 1);
        List<AccountMsgLog> accountMsgLogList = accountMsgLogMapper.selectByExampleWithBLOBsWithRowbounds(msgLogExample, rowBounds);
        if (CollectionUtils.isEmpty(accountMsgLogList)) {
            throw new IllegalArgumentException("未找到交易报文记录");
        }
        lastAccountMsgLog = accountMsgLogList.get(0);
        if (component.isReversal()) {
            // 当前为冲正交易
            if (TransConstant.IS_FLAG_1.equals(lastAccountLog.getReversalFlag())) {
                // 上一笔交易记录为 1-冲正交易
                if (TransConstant.TRAN_STATUS_S.equals(lastAccountLog.getTranStatus())) {
                    return true;
                } else if (TransConstant.TRAN_STATUS_F.equals(lastAccountLog.getTranStatus())) {
                    return false;
                } else if (TransConstant.TRAN_STATUS_U.equals(lastAccountLog.getTranStatus())) {
                    return isTranSucceed(lastAccountLog);
                }
            } else if (TransConstant.IS_FLAG_0.equals(lastAccountLog.getReversalFlag())) {
                // 上一笔交易记录为 0-正常交易
                return false;
            }
        } else {
            // 当前为正常交易
            if (TransConstant.IS_FLAG_1.equals(lastAccountLog.getReversalFlag())) {
                // 上一笔交易记录为 1-冲正交易
                return false;
            } else if (TransConstant.IS_FLAG_0.equals(lastAccountLog.getReversalFlag())) {
                // 上一笔交易记录为 0-正常交易
                if (TransConstant.TRAN_STATUS_S.equals(lastAccountLog.getTranStatus())) {
                    return true;
                } else if (TransConstant.TRAN_STATUS_F.equals(lastAccountLog.getTranStatus()) || TransConstant.TRAN_STATUS_R.equals(lastAccountLog.getTranStatus())) {
                    return false;
                } else if (TransConstant.TRAN_STATUS_U.equals(lastAccountLog.getTranStatus())) {
                    return isTranSucceed(lastAccountLog);
                }
            }
        }
        throw new IllegalArgumentException("未知的交易状态：" + lastAccountLog.getTranStatus());
    }

    /**
     * 重新解析响应报文
     */
    private void reParseResponseMsg() {
        // 查询报文记录
        Assert.notNull(lastAccountLog, "获取交易记录为空");
        if (ClientHandler.LoggingType.NONE.equals(component.getLoggingType())) {
            throw new IllegalArgumentException("当前交易不记录报文，不能重新解析");
        }
        Assert.isTrue(TransConstant.TRAN_STATUS_S.equals(lastAccountMsgLog.getTranStatus()), "交易状态不正确，应为S");
        if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
            Assert.hasText(lastAccountMsgLog.getRespMsg(), "获取响应报文为空，不能重新解析");
            component.setRespMsg(lastAccountMsgLog.getRespMsg());
        } else if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
            String respMsg = FileUtil.readUtf8String(getLogFilePath(lastAccountMsgLog.getTranDate(), lastAccountLog.getSysCode(), lastAccountLog.getMsgCode(), lastAccountMsgLog.getId()));
            Assert.hasText(respMsg, "获取报文文件内容为空，不能重新解析");
            String[] msgArr = StringUtils.split(respMsg, "\n\n");
            Assert.isTrue(msgArr != null && msgArr.length == 2, "解析报文文件出错，格式不正确");
            respMsg = msgArr[1];
            Assert.hasText(respMsg, "获取响应报文为空，不能重新解析");
            component.setRespMsg(respMsg);
        } else {
            throw new IllegalArgumentException("未知的报文记录方式：" + component.getLoggingType());
        }

        // 解析响应报文
        parseResponseMsg();
    }

    /**
     * 判断交易是否已成功
     * <p>
     * 交易状态为“U-未知”时调用
     *
     * @param lastAccountLog 上一次交易记录
     * @return true-已成功，false-未成功
     */
    protected boolean isTranSucceed(AccountLog lastAccountLog) {
        throw new IllegalStateException(lastAccountLog.getId() + "-未知的交易状态，请联系人工处理");
    }

    /**
     * 组装请求报文
     */
    protected abstract void buildRequestMsg();

    /**
     * 保存交易记录
     */
    private void writeAccountLog() {
        if (lastAccountLog == null || (!getReversalFlag().equals(lastAccountLog.getReversalFlag()) && (TransConstant.TRAN_STATUS_S.equals(lastAccountLog.getTranStatus()) || TransConstant.TRAN_STATUS_R.equals(lastAccountLog.getTranStatus())))) {
            // 新增一笔交易记录
            AccountLog accountLog = new AccountLog();
            accountLog.setContractId(component.getContractId());
            accountLog.setDetailId(component.getDetailId());
            accountLog.setContractNo(component.getContractNo());
            accountLog.setProductNo(component.getProductNo());
            accountLog.setTranDate(this.tranDate);
            accountLog.setTranTime(this.tranTime);
            accountLog.setSysCode(component.getSysCode());
            accountLog.setMsgCode(component.getMsgCode());
            accountLog.setTranStatus(TransConstant.TRAN_STATUS_U);
            accountLog.setReversalFlag(getReversalFlag());
            // TODO 设置当前操作人ID
            accountLog.setCreateUserId(null);
            accountLog.setCreateTime(DateUtil.getDateTime());
            accountLogMapper.insertSelective(accountLog);
            currAccountLog = accountLog;
        } else {
            // 更新上一次的交易记录
            Assert.isTrue(getReversalFlag().equals(lastAccountLog.getReversalFlag()), "数据异常，请联系人工处理");
            Assert.isTrue(TransConstant.TRAN_STATUS_F.equals(lastAccountLog.getTranStatus()) || TransConstant.TRAN_STATUS_U.equals(lastAccountLog.getTranStatus()), "数据异常，请联系人工处理");
            AccountLog accountLog = new AccountLog();
            accountLog.setId(lastAccountLog.getId());
            accountLog.setTranDate(this.tranDate);
            accountLog.setTranTime(this.tranTime);
            accountLog.setTranStatus(TransConstant.TRAN_STATUS_U);
            // TODO 设置当前操作人ID
            accountLog.setUpdateUserId(null);
            accountLog.setUpdateTime(DateUtil.getDateTime());
            accountLogMapper.updateByPrimaryKeySelective(accountLog);
            currAccountLog = accountLog;
        }

        AccountMsgLog accountMsgLog = new AccountMsgLog();
        accountMsgLog.setAcctId(currAccountLog.getId());
        accountMsgLog.setTranDate(currAccountLog.getTranDate());
        accountMsgLog.setTranTime(currAccountLog.getTranTime());
        accountMsgLog.setTranStatus(currAccountLog.getTranStatus());
        accountMsgLog.setReqSeqNo(component.getReqSeqNo());
        // TODO 设置当前操作人ID
        accountMsgLog.setCreateUserId(null);
        accountMsgLog.setCreateTime(DateUtil.getDateTime());
        if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
            accountMsgLog.setReqMsg(component.getReqMsg());
        }
        accountMsgLogMapper.insertSelective(accountMsgLog);
        currAccountMsgLog = accountMsgLog;

        if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
            FileUtil.writeUtf8String(component.getReqMsg(), getLogFilePath(currAccountMsgLog.getTranDate(), currAccountLog.getSysCode(), currAccountLog.getMsgCode(), currAccountMsgLog.getId()));
        }
    }

    /**
     * 发送报文
     */
    protected abstract void sendMsg();

    /**
     * 解析响应报文
     */
    protected abstract void parseResponseMsg();

    /**
     * 更新交易记录
     */
    private void updateAccountLog(Throwable th) {
        if (currAccountLog == null) {
            logger.warn("当前交易记录为空");
            return;
        }
        String tranStatus = null;
        if (sendMsgComplete) {
            if (th == null) {
                if (component.isSuccess()) {
                    tranStatus = TransConstant.TRAN_STATUS_S;
                } else {
                    tranStatus = TransConstant.TRAN_STATUS_F;
                }
            } else {
                tranStatus = TransConstant.TRAN_STATUS_U;
            }
        } else {
            // 消息未发送，发送前就报错了
            tranStatus = TransConstant.TRAN_STATUS_F;
        }

        AccountLog accountLog = new AccountLog();
        accountLog.setId(currAccountLog.getId());
        accountLog.setTranStatus(tranStatus);
        accountLog.setUpdateTime(DateUtil.getDateTime());
        accountLogMapper.updateByPrimaryKeySelective(accountLog);

        if (currAccountMsgLog == null) {
            logger.warn("当前交易报文记录为空");
            return;
        }
        AccountMsgLog accountMsgLog = new AccountMsgLog();
        accountMsgLog.setId(currAccountMsgLog.getId());
        accountMsgLog.setTranStatus(tranStatus);
        accountMsgLog.setRetCode(component.getRetCode());
        if (StringUtils.isEmpty(component.getRetMsg())) {
            if (th != null) {
                component.setRetMsg(ExceptionUtils.getRootCauseMessage(th));
            } else if (component.isSuccess()) {
                component.setRetMsg(GxpConstant.RET_MSG_SUCCESS);
            } else {
                component.setRetMsg("交易失败，未知原因");
            }
        }
        accountMsgLog.setRetMsg(component.getRetMsg());
        accountMsgLog.setRespSeqNo(component.getRespSeqNo());
        accountMsgLog.setUpdateTime(DateUtil.getDateTime());
        if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
            accountMsgLog.setRespMsg(component.getRespMsg());
        }
        accountMsgLogMapper.updateByPrimaryKeySelective(accountMsgLog);

        if (component.isReversal()) {
            // 当前交易为冲正交易
            logger.info("当前交易为冲正交易");
            // 查询当前冲正交易之前的已成功的记录，更新状态为以冲正
            AccountLogExample example = new AccountLogExample();
            example.createCriteria().andContractIdEqualTo(component.getContractId())
                    .andDetailIdEqualTo(component.getDetailId()).andProductNoEqualTo(component.getProductNo())
                    .andIdNotEqualTo(currAccountLog.getId()).andTranStatusEqualTo(TransConstant.TRAN_STATUS_S)
                    .andReversalFlagEqualTo(TransConstant.IS_FLAG_0);
            List<AccountLog> previousAccountLogList = accountLogMapper.selectByExample(example);
            if (!CollectionUtils.isEmpty(previousAccountLogList)) {
                logger.info("更新正向交易状态");
                List<Integer> accountLogIdList = previousAccountLogList.stream().map(AccountLog::getId).collect(Collectors.toList());

                AccountLog logRecord = new AccountLog();
                logRecord.setTranStatus(TransConstant.TRAN_STATUS_R);
                // TODO 设置当前操作人ID
                logRecord.setUpdateUserId(null);
                logRecord.setUpdateTime(DateUtil.getDateTime());
                example.clear();
                example.createCriteria().andIdIn(accountLogIdList).andTranStatusEqualTo(TransConstant.TRAN_STATUS_S);
                accountLogMapper.updateByExampleSelective(logRecord, example);

                AccountMsgLog msgLogRecord = new AccountMsgLog();
                msgLogRecord.setTranStatus(TransConstant.TRAN_STATUS_R);
                // TODO 设置当前操作人ID
                msgLogRecord.setUpdateUserId(null);
                msgLogRecord.setUpdateTime(DateUtil.getDateTime());
                AccountMsgLogExample msgLogExample = new AccountMsgLogExample();
                msgLogExample.createCriteria().andAcctIdIn(accountLogIdList).andIdNotEqualTo(currAccountMsgLog.getId()).andTranStatusEqualTo(TransConstant.TRAN_STATUS_S);
                accountMsgLogMapper.updateByExampleSelective(msgLogRecord, msgLogExample);
            }
        }

        if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
            FileWriter.create(new File(getLogFilePath(currAccountMsgLog.getTranDate(), currAccountLog.getSysCode(), currAccountLog.getMsgCode(), currAccountMsgLog.getId())), CharsetUtil.CHARSET_UTF_8).append("\n\n" + component.getRespMsg());
        }
    }

    /**
     * 报文记录到文件时，获取文件路径
     *
     * @param tranDate     交易日期
     * @param sysCode      系统编码
     * @param msgCode      消息编码
     * @param accountMsgId ACCOUNT_MSG_LOG.ID
     * @return
     */
    private String getLogFilePath(String tranDate, String sysCode, String msgCode, Integer accountMsgId) {
        return "/home/ssm/logs/trans/" + tranDate + File.separator + sysCode + File.separator + msgCode + "_" + accountMsgId + ".txt";
    }

    /**
     * 获取冲正标志
     *
     * @return 0-记账交易，1-冲正交易
     */
    private String getReversalFlag() {
        if (component.isReversal()) {
            return TransConstant.IS_FLAG_1;
        } else {
            return TransConstant.IS_FLAG_0;
        }
    }

}
